#include <fuzzer/FuzzedDataProvider.h>
#include <string>

#include "jq.h"
#include "jv.h"


const char *jq_progs[] = {
    ". / \", \"",
    ".[]",
    "$ENV.PAGER",
    ".[0]",
    ". < 0.12345678901234567890123456788",
    ".[] == 1",
    ".[] | (1 / .)?",
    "10 / . * 3",
    "[1,2,empty,3]",
    "1, empty, 2",
    "[.,1]|until(.[0] < 1; [.[0] - 1, .[1] * .[0]])|.[1]",
    ".[-2:]",
    ".[-2]",
    ".[2]",
    "[ .[] | . * 2]",
    ".[2:4]",
    "(. + 2) * 5",
    ".[:3]",
    ".[4,2]",
    "42 and \"a string\"",
    "4 - .a",
    ". < 5",
    ".. | .a?",
    "[.[] | .a?]",
    ".a + 1",
    "{a: 1} + {b: 2} + {c: 3} + {a: 42}",
    ".a + .b",
    ".a = .b",
    ".a |= .b",
    "add",
    "all",
    ".a + null",
    "any",
    ".[] as [$a, $b] | {a: $a, b: $b}",
    ". as [$a, $b, {c: $c}] | $a + $b + $c",
    ".[] as {$a, $b, c: {$d}} ?// {$a, $b, c: [{$e}]} | {$a, $b, $d, $e}",
    ".[] as {$a, $b, c: {$d, $e}} ?// {$a, $b, c: [{$d, $e}]} | {$a, $b, "
    "$d, $e}",
    ".[] as [$a] ?// [$b] | if $a != null then error(\"err: \\($a)\") else "
    "{$a,$b} end",
    ". as $big | [$big, $big + 1] | map(. > "
    "10000000000000000000000000000000)",
    ". as $dot|fromstream($dot|tostream)|.==$dot",
    ". as $i|[(.*2|. as $i| $i), $i]",
    "ascii_upcase",
    ".bar as $x | .foo | . + $x",
    "@base64",
    "@base64d",
    ". == {\"b\": {\"d\": (4 + 1e-20), \"c\": 3}, \"a\":1}",
    "bsearch(0)",
    "bsearch(4) as $ix | if $ix < 0 then .[-(1+$ix)] = 4 else . end",
    "capture(\"(?<a>[a-z]+)-(?<n>[0-9]+)\")",
    "capture(\"(?<a>[a-z]+)-(?<n>[0-9]+)\")",
    "combinations",
    "combinations(2)",
    "contains(\"bar\")",
    "contains([\"baz\", \"bar\"])",
    "contains([\"bazzzzz\", \"bar\"])",
    "contains({foo: 12, bar: [{barp: 15}]})",
    "def addvalue(f): f as $x | map(. + $x); addvalue(.[0])",
    "def addvalue(f): . + [f]; map(addvalue(.[0]))",
    "def while(cond; update): def _while: if cond then ., (update | "
    "_while) else empty end; _while; [while(.<100; .*2)]",
    "del(.[1, 2])",
    "del(.foo)",
    "delpaths([[\"a\",\"b\"]])",
    "empty // 42",
    "[.[]|endswith(\"foo\")]",
    "env.PAGER",
    "explode",
    ". == false",
    "(false, null, 1) // 42",
    "(false, null, 1) | . // 42",
    "flatten",
    "flatten(1)",
    "floor",
    ".[\"foo\"]",
    ".[\"foo\"]?",
    ".foo",
    ".foo?",
    ".foo[]",
    "[.foo?]",
    ".foo += 1",
    ".foo // 42",
    ".foo, .bar",
    "foreach .[] as $item (0; . + $item)",
    "foreach .[] as $item (0; . + $item; [$item, . * 2])",
    "foreach .[] as $item (0; . + 1; {index: ., $item})",
    "fromdate",
    "from_entries",
    "fromstream(1|truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]]))",
    "getpath([\"a\",\"b\"])",
    "[getpath([\"a\",\"b\"], [\"a\",\"c\"])]",
    "group_by(.foo)",
    "[.[] | gsub(\", \"; \":\")]",
    "gsub(\"$\"; \"a\"; \"g\")",
    "gsub(\"^\"; \"a\")",
    "[gsub(\"(?<a>.)\"; \"\\(.a|ascii_upcase)\", \"\\(.a|ascii_downcase)\", "
    "\"c\")]",
    "gsub(\"^.*?a\"; \"b\")",
    "gsub(\"^.*a\"; \"b\")",
    "gsub(\"a\";\"b\")",
    "gsub(\"\"; \"a\"; \"g\")",
    "gsub(\"\"; \"a\"; \"g\")",
    "gsub(\"[^a-z]*(?<x>[a-z]*)\"; \"Z\\(.x)\")",
    "gsub(\"\\b(?<x>.)\"; \"\\(.x|ascii_downcase)\")",
    "gsub(\"(?<d>\\d)\"; \":\\(.d);\")",
    "gsub(\"^\"; \"\"; \"g\")",
    "[gsub(\"p\"; \"a\", \"b\")]",
    "gsub(\"(?=u)\"; \"u\")",
    "gsub(\"(.*)\"; \"\"; \"x\")",
    "gsub(\"(?<x>.)[^a]*\"; \"+\\(.x)-\")",
    "gsub(\"(?<x>.)(?<y>[0-9])\"; \"\\(.x|ascii_downcase)\\(.y)\")",
    "@html",
    "if . == 0 then   \"zero\" elif . == 1 then   \"one\" else   \"many\" "
    "end",
    "implode",
    "index(\", \")",
    "index(1)",
    "index([1,2])",
    "indices(\", \")",
    "indices(1)",
    "indices([1,2])",
    ".[] | (infinite * .) < 0",
    "infinite, nan | type",
    ".[] | in({\"foo\": 42})",
    "inside({\"foo\": 12, \"bar\":[1,2,{\"barp\":12, \"blip\":13}]})",
    "inside({\"foo\": 12, \"bar\":[1,2,{\"barp\":12, \"blip\":13}]})",
    "inside(\"foobar\")",
    "inside([\"foobar\", \"foobaz\", \"blarp\"])",
    "inside([\"foobar\", \"foobaz\", \"blarp\"])",
    "isempty(.[])",
    "isempty(.[])",
    "isempty(empty)",
    "join(\" \")",
    "join(\", \")",
    "keys",
    "keys",
    ".[] | length",
    "[limit(3;.[])]",
    "[.[]|ltrimstr(\"foo\")]",
    "map(., .)",
    "map(.+1)",
    "map([., . == 1]) | tojson",
    "map(abs)",
    "map(has(2))",
    "map(has(\"foo\"))",
    "map(in([0,1]))",
    "map(select(. >= 2))",
    "map(type)",
    "map_values(.+1)",
    "map_values(. // empty)",
    "match(\"(abc)+\"; \"g\")",
    "[match(\"a\"; \"gi\")]",
    "[match(\".+?\\b\")]",
    "[match([\"(bar)\"])]",
    "match(\"foo\")",
    "[match([\"foo (?<bar123>bar)? foo\", \"ig\"])]",
    "match(\"foo (?<bar123>bar)? foo\"; \"ig\")",
    "match([\"foo\", \"ig\"])",
    "[match(\"\"; \"g\")]",
    "[ match(\".\"; \"g\")] | length",
    "[match(\"( )*\"; \"gn\")]",
    "max_by(.foo)",
    "min",
    ".[] | .name",
    ".[]|numbers",
    "[path(..)]",
    "path(.a[0].b)",
    "[paths]",
    "[paths(type == \"number\")]",
    "pick(.[2], .[0], .[0])",
    "pick(.a, .b.c, .x)",
    "[range(0; 10; -1)]",
    "[range(0; 10; 3)]",
    "[range(0; -5; -1)]",
    "[range(2; 4)]",
    "range(2; 4)",
    "[range(4)]",
    "[range(.)]|[first, last, nth(5)]",
    "recurse",
    "recurse(. * .; . < 20)",
    "recurse(.foo[])",
    "reduce .[] as [$i,$j] (0; . + $i * $j)",
    "reduce .[] as $item (0; . + $item)",
    "reduce .[] as {$x,$y} (null; .x += $x | .y += [$y])",
    "[repeat(.*2, error)?]",
    "reverse",
    "rindex(\", \")",
    "rindex(1)",
    "rindex([1,2])",
    "[.[]|rtrimstr(\"foo\")]",
    "[.[] | scan(\", \")]",
    "[.[] | scan(\"b+\"; \"i\")]",
    "scan(\"c\")",
    ".[] | select(.id == \"second\")",
    "(..|select(type==\"boolean\")) |= if . then 1 else 0 end",
    "setpath([0,\"a\"]; 1)",
    "setpath([\"a\",\"b\"]; 1)",
    "setpath([\"a\",\"b\"]; 1)",
    "@sh \"echo \\(.)\"",
    "sort",
    "sort_by(.foo)",
    "sort_by(.foo, .bar)",
    "split(\", *\"; null)",
    "splits(\", *\")",
    "sqrt",
    "[.[]|startswith(\"foo\")]",
    "strptime(\"%Y-%m-%dT%H:%M:%SZ\")",
    "strptime(\"%Y-%m-%dT%H:%M:%SZ\")|mktime",
    "[sub(\"(?<a>.)\"; \"\\(.a|ascii_upcase)\", \"\\(.a|ascii_downcase)\")]",
    "[sub(\"(?<a>.)\"; \"\\(.a|ascii_upcase)\", \"\\(.a|ascii_downcase)\", "
    "\"c\")]",
    "[sub(\"a\"; \"b\", \"c\")]",
    "sub(\"[^a-z]*(?<x>[a-z]+)\"; \"Z\\(.x)\"; \"g\")",
    "[.[]|[[sub(\", *\";\":\")], [gsub(\", *\";\":\")], [scan(\", *\")]]]",
    "[.[]|[[sub(\", +\";\":\")], [gsub(\", +\";\":\")], [scan(\", +\")]]]",
    "sub(\"^(?<head>.)\"; \"Head=\\(.head) Tail=\")",
    "[test(\"ā\")]",
    ".[] | test(\"a b c # spaces are ignored\"; \"ix\")",
    "test(\"foo\")",
    "to_entries",
    "[., tojson]",
    "[.[]|tojson]",
    "[.[]|tojson|fromjson]",
    ".[] | tonumber",
    "[.[] | tonumber?]",
    ".[] | tostring",
    "[.[]|tostring]",
    "transpose",
    "[true, false | not]",
    "(true, false) or false",
    "(true, true) and (true, false)",
    "truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]])",
    "[.[]|try .a]",
    "unique",
    "unique_by(.foo)",
    "unique_by(length)",
    ".user, .projects[]",
    "[.user, .projects[]]",
    "{(.user): .titles}",
    "{user, title: .titles[]}",
    "utf8bytelength",
    "walk(if type == \"array\" then sort else . end)",
    "walk( if type == \"object\" then with_entries( .key |= sub( \"^_+\"; "
    "\"\") ) else . end )",
    "[while(.<100; .*2)]",
    "with_entries(.key |= \"KEY_\" + .)",
    ". - [\"xml\", \"yaml\"]",
};

// Fuzzer inspired by /src/jq_test.c
// The goal is to have the fuzzer execute the functions:
// jq_compile -> jv_parse -> jq_next.
extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
  FuzzedDataProvider fdp(data, size);
  std::string parse_payload1 = fdp.ConsumeRandomLengthString();
  std::string parse_payload2 = fdp.ConsumeRandomLengthString();

  int idx = fdp.ConsumeIntegralInRange<int>(
      0, (sizeof(jq_progs) / sizeof(char *)) - 1);

  jq_state *jq = NULL;
  jq = jq_init();
  if (jq != NULL) {
    jq_set_attr(jq, jv_string("JQ_ORIGIN"), jv_string("/tmp/"));

    if (jq_compile(jq, jq_progs[idx])) {
      // Process to jv_parse and then jv_next
      jv input = jv_parse(parse_payload1.c_str());
      if (jv_is_valid(input)) {
        jq_start(jq, input, 0);
        jv next = jv_parse(parse_payload2.c_str());
        if (jv_is_valid(next)) {
          jv actual = jq_next(jq);
          jv_free(actual);
        }
        jv_free(next);
      } else {
        // Only free if input is invalid as otherwise jq_teardown
        // frees it.
        jv_free(input);
      }
    }
  }
  jq_teardown(&jq);

  return 0;
}
